完成自由散步與路線顯示後,接下來要實作的是「推薦散步路線」。
自由散步的流程比較單純,開始後持續記錄 GPS,結束時再計算距離、時間與速度即可。
但推薦散步不太一樣,在開始之前就要先知道使用者想怎麼走,例如:
接下來要做的,就是把這些需求轉換成一條真正可以步行的路線。

目前我先把推薦流程拆成以下幾個步驟:
各層負責的內容如下:
| 元件 | 負責內容 |
|---|---|
| React Native App | 收集需求、顯示推薦路線 |
| 後端 | 整理需求、建立候選、呼叫 Google API |
| Google Places | 搜尋附近符合偏好的地點 |
| Google Routes | 計算實際可步行的道路 |
| 資料庫 | 儲存推薦結果與路線資料 |
App 不會直接呼叫 Google Places 或 Google Routes,而是先把使用者輸入的條件送到後端:
type GenerateRouteRequest = {
start: {
latitude: number;
longitude: number;
};
targetDurationSeconds?: number;
targetDistanceMeters?: number;
returnToStart: boolean;
preferences: RoutePreference[];
};
一開始我以為只要把散步條件交給 Google Routes,就能直接得到推薦路線。
但 Google Routes 解決的是「已經知道要去哪裡之後,該怎麼走」,並不會替我決定目的地。
推薦散步通常只有起點是確定的,而時間、距離和偏好都不是 Routes API 可以直接拿來決定目的地的條件,因此後端還需要先完成一段轉換:
30 分鐘
↓
預估散步距離
↓
Google Places 搜尋附近公園
↓
取得公園座標
↓
建立起點、終點與 waypoint
↓
交給 Google Routes 計算道路
Google Routes 除了起點與終點,也支援途中必須經過的 waypoint。
因此從 Places 找到合適的地點後,可以把這些地點整理成 waypoint,再交給 Routes 規劃真正的步行路線。
Routes 不會只是把這些座標用直線連起來,而是會根據實際道路與步行限制,計算出可以行走的路線。
推薦路線支援兩種目標:
如果使用者直接輸入距離,後端就可以直接拿這個距離作為後續規劃的參考。
但如果使用者只輸入時間,系統還不知道大概要搜尋多大的範圍,也不知道 waypoint 應該放多遠。
因此,我先使用固定步行速度 1.25 m/s,將時間換算成預估距離:
參考距離 = 目標時間 × 預估步行速度
const WALKING_SPEED_METERS_PER_SECOND = 1.25;
const targetDistance =
request.targetDistanceMeters ??
request.targetDurationSeconds! *
WALKING_SPEED_METERS_PER_SECOND;
假如使用者想走 30 分鐘:
30 分鐘 = 1800 秒
1800 × 1.25 = 2250 公尺
這裡算出的 2250 公尺並不是最後真正會走的距離,而是後續產生候選路線時使用的參考值。
有了這個距離後系統才知道 Places 大概要搜尋多大的範圍,以及候選 waypoint 大概要落在哪裡。
有了參考距離後,如果使用者選擇了「公園」、「咖啡店」等偏好,就會先透過 Google Places 搜尋附近符合條件的地點。
例如:
偏好:公園
↓
Google Places
↓
大安森林公園
榮星花園
青年公園
...
Places API 回傳的不只是地點名稱,也包含每個地點的座標。
例如:
{
"displayName": {
"text": "大安森林公園"
},
"location": {
"latitude": 25.0335,
"longitude": 121.5356
}
}
這裡真正需要的是地點的經緯度,後續才能拿來建立 waypoint。
取得地點座標後,就可以整理出後續規劃路線需要的起點、終點與 waypoint。
例如使用者選擇回到起點,路線可能會組成:
Origin
↓
Waypoint
↓
Destination(Origin)
也就是從起點出發,途中經過指定地點,最後再回到原本的起點。
如果不需要回到起點,則可以把最後一個候選地點當成 Destination,其餘地點再作為途中經過的 waypoint。
這些座標整理完成後,就可以交給 Google Routes 計算真正的步行道路。
在 Google Routes API 中,中途需要經過的 waypoint 會放在 intermediates 欄位裡。
因此需要先將前面整理好的 waypoint 轉成 API 需要的格式:
const intermediates = intermediateWaypoints.map(
waypoint => ({
location: {
latLng: {
latitude: waypoint.latitude,
longitude: waypoint.longitude,
},
},
}),
);
接著再將起點、終點與 intermediates 一起送給 Google Routes:
const response = await fetch(
'https://routes.googleapis.com/directions/v2:computeRoutes',
{
...
body: JSON.stringify({
origin: {
location: {
latLng: request.start,
},
},
destination: {
location: {
latLng: destination,
},
},
intermediates,
travelMode: 'WALK',
}),
},
);
Routes API 會回傳規劃完成的路線,目前主要會使用三個欄位:
到這裡,就完成了從使用者需求到實際步行路線的轉換:
使用者需求
↓
參考距離
↓
Google Places
↓
地點座標
↓
Waypoint
↓
Google Routes
↓
可步行路線
不過現在只是成功算出一條可以走的路線,還不能保證它就是最符合使用者需求的一條。
例如使用者想走 3 公里,實際算出的路線可能只有 1 公里,也可能超過 4 公里。
因此明天要分享的是如何挑出最適合的一條路線。